/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.scheduler.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Date;

import jp.aist.gtrc.plus.scheduler.node.NodeAllocateRequest;

import org.apache.log4j.Logger;

import condor.classad.*;

public class ClassAdEvaluator {

	private final String adFilePath;
	private final RecordExpr rec;
	private static final Logger logger = Logger.getLogger(ClassAdEvaluator.class);
	private static boolean initialized = false;
	private static RecordExpr currentRec;
	private static final FakeStdErrStream fakeStdErr = new FakeStdErrStream();

	private static synchronized void initClassAd(String filepath) {

		if (initialized) {
			return;
		}

		/*
		 * Load methods in ClassAdFuncsForPluS
		 */
		final String CLASSAD_FUNCS_CLASS
			= ClassAdFuncsForPluS.class.getCanonicalName();
	    if (ClassAd.loadJavaLibrary(CLASSAD_FUNCS_CLASS) == false) {
	    	assert(false);
	    	logger.warn("Cannot load " + CLASSAD_FUNCS_CLASS + ", ignored");
	    }

		/*
		 * Must call setLoggerPath() before using plus_rsvutil() from ClassAd
		 */
	    int index = filepath.lastIndexOf('/');
	    if (index != -1) {
	    	filepath = filepath.substring(0, index);
	    }
		ClassAdFuncsForPluS.setLoggerPath(filepath);

		initialized = true;

	}

	public ClassAdEvaluator(String adFilePath, NodeAllocateRequest allocReq) throws FileNotFoundException {

		this.adFilePath = adFilePath;
		this.rec = makeRecord(adFilePath);

		// add default reserve attributes
		String owner = allocReq.getRequester();
		Date start = allocReq.getStartTime().getTime();
		Date end = allocReq.getEndTime().getTime();
		rec.insertAttribute("PLUS_RSV_OWNER", Constant.getInstance(owner));
		rec.insertAttribute("PLUS_RSV_START", Constant.getInstance(start));
		rec.insertAttribute("PLUS_RSV_END", Constant.getInstance(end));

	}

	private RecordExpr makeRecord(String adFilePath) throws FileNotFoundException {

		initClassAd(adFilePath);

		InputStream in;
		File f = new File(adFilePath);
		if (f.exists() && !f.canRead()) {
			throw new IllegalStateException(adFilePath + " isn't readable");
		}
		in = new FileInputStream(f);

		/*
		 * NOTE: parser.parse() writes error message to System.err.
		 * We get the message by fakeStdErr.getOutput().
		 */
		fakeStdErr.overrideStderr();
		ClassAdParser parser = new ClassAdParser(in);
		Expr expr = parser.parse();
		fakeStdErr.recoverStderr();
		String err = fakeStdErr.getOutput();

		if ((expr == null) || (err.length() > 0)) {
			throw new IllegalArgumentException(
					String.format("in %s\n%s", adFilePath, err));
		}
		if (!(expr instanceof RecordExpr)) {
			throw new IllegalArgumentException(
					String.format("%s must be one RecordExpression, it's %s.",
							adFilePath,
							expr.getClass().getSimpleName()));
		}

		return (RecordExpr)expr;

	}

	public void insertAttribute(String name, Expr expr) {

		// NOTE: replace expr if name is already set
		rec.insertAttribute(name, expr);

	}

	public boolean hasAttr(String attrName) {

		return (rec.lookup(attrName) != null);

	}

	private synchronized Expr eval(Expr expr) {

		Env env = new Env();
		env.push(rec);

		logger.debug("try evaluate " + expr.toString() + " in " + rec.toString());

		/*
		 * NOTE: make this method synchronized to protect currentRec.
		 * getCurrentRecord() is not synchronized.
		 */
		currentRec = rec;
		Expr result = expr.eval(env);
		currentRec = null;

		logger.debug("result is " + result.toString());
		return result;

	}

	public Expr eval(String exp) {

		assert(rec != null);

		Expr value = rec.lookup(exp);
		if (value != null) {
			Expr result = eval(value);
			if ((result.type == Expr.UNDEFINED) || (result.type == Expr.ERROR)) {
				Constant c = (Constant)result;
				String msg = String.format("in %s\n%s", adFilePath, c.annotation());
				logger.warn(msg);
				throw new IllegalArgumentException(msg);
			}
			return result;
		} else {
			String msg = "not found definition of " + exp + " in " + adFilePath;
			logger.warn(msg);
			throw new IllegalArgumentException(msg);
		}

	}

	static final RecordExpr getCurrentRecord() {

		return (currentRec != null) ? currentRec : new RecordExpr();

	}

}
